#include <iostream>
#include <vector>
#include <cmath>

#include "wave_reader.h"
#include "wave_writer.h"

const double PI = 3.14159265359;

double hFunc(unsigned k, unsigned n, unsigned M);
double gFunc(unsigned k, unsigned n, unsigned M);
double wFunc(unsigned n, unsigned M);
std::vector<double> MDCT(const std::vector<double> &timeDomain);
std::vector<double> IMDCT(const std::vector<double> &current, 
                          const std::vector<double> &prev);
//std::vector<double> Conv(const std::vector<double> &f1, const std::vector<double> &f2);

int main()
{
  std::cout << "Start!" << std::endl;

  WaveReader::WaveReaderPtr reader = WaveReader::GetInstance();
  WaveWriter::WaveWriterPtr writer = WaveWriter::GetInstance();
  
  writer->SetMeta(reader->GetRiff(), reader->GetWave());

  int dataSize = reader->GetDataSize() / 2;
  int window = 2048;

  //////////////////////////////////////////////////////////////////////////
  std::vector<double> prevBuff;
  for (int i = 0; i < dataSize; i += window / 2)
  {
    std::vector<double> inBuff;
    for (int j = 0; j < window; ++j)
    {
      inBuff.push_back(reader->at(j + i) * wFunc(j, window / 2));
    }

    std::vector<double> mdctBuff = MDCT(inBuff);
    if (i == 0)
    {
      prevBuff = mdctBuff;
      continue;
    }

    std::vector<double> outBuff = IMDCT(mdctBuff, prevBuff);
    prevBuff = mdctBuff;

    for (int j = 0; j < outBuff.size(); ++j)
    {
      writer->AddValToData(outBuff.at(j));
    }
  }

  //////////////////////////////////////////////////////////////////////////
  
  writer->SaveWave();
  
  std::cout << "End!" << std::endl;
  return 0;
}

double hFunc(unsigned k, unsigned n, unsigned M)
{
  return cos(PI / M * (n + 0.5 + M / 2) * (k + 0.5));
    //wFunc(n, M) * sqrt(2.0 / double(M)) * cos((2*n + M + 1) * (2*k + 1) * PI / 4*M);
}

double gFunc(unsigned k, unsigned n, unsigned M)
{
  return hFunc(k, 2*M - 1 - n, M);
}

double wFunc(unsigned n, unsigned M)
{
  return 1.0;//0.5 - 0.5 * cos(n * (2*PI) / (2*M));
}

std::vector<double> MDCT(const std::vector<double> &timeDomain)
{
  int M = timeDomain.size() / 2;
  std::vector<double> res;

  res.insert(res.begin(), M, 0.0);
  for (int k = 0; k < M; ++k)
  {
    for (int n = 0; n < 2*M; ++n)
    {
      res[k] += timeDomain.at(n) * hFunc(k, n, M);
    }
  }

  return res;
}

std::vector<double> IMDCT(const std::vector<double> &current, 
                          const std::vector<double> &prev)
{
  int M = current.size();
  std::vector<double> res;

  res.insert(res.begin(), M, 0.0);
  for (int n = 0; n < M; ++n)
  {
    for (int k = 0; k < M; ++k)
    {
      res[n] += current.at(k) * hFunc(k, n, M) + prev.at(k) * hFunc(k, n + M, M);
    }
    res[n] /= M;
  }

  return res;
}